package com.cat.index_processor

import com.cat.annotation.Index
import com.cat.annotation.IndexEntry
import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import java.lang.IllegalStateException
import java.lang.StringBuilder
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.tools.Diagnostic


@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions(IndexProcessor.KAPT_KOTLIN_GENERATED_OPTION_NAME)
class IndexProcessor : AbstractProcessor() {


    companion object {
        const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
    }

    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return mutableSetOf(Index::class.java.name)
    }

    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.latest()
    }


    private var elementUtils: Elements? = null
    private var messager: Messager? = null
    private var mFiler: Filer? = null


    private val INDEX_CLASS_NAME = "IndexRepo"

    private val INDEX_CLASS_FIELD_NAME = "indexDB"

    @Synchronized
    override fun init(processingEnv: ProcessingEnvironment) {
        super.init(processingEnv)
        mFiler = processingEnv.filer
        elementUtils = processingEnv.elementUtils
        messager = processingEnv.messager
    }

    override fun process(
        annotations: MutableSet<out TypeElement>?,
        roundEnv: RoundEnvironment?
    ): Boolean {

        val elements = roundEnv?.getElementsAnnotatedWith(Index::class.java)

        val indexedElements = roundEnv?.getElementsAnnotatedWith(IndexEntry::class.java)
        //recursive find the parentIndexId and id


        var pkgName = ""
        var clsName: String
        var className: ClassName

        //keyword -------> t1,t2,t3
        val keywordIndexList = hashMapOf<String, HashSet<ClassName>?>()

        elements?.forEach { element ->

            if (pkgName.isEmpty())
                pkgName = getPackageName(element)
                    ?: throw IllegalStateException("cannot retrieve valid packageName!")
            clsName = element.simpleName.toString()
            className = ClassName(pkgName, clsName)
            val index = element.getAnnotation(Index::class.java)
            val keywords = index.keywords.split("/")
            //todo link index
            val link = index.link
            keywords.forEach {
                var keywordsList = keywordIndexList[it]
                if (keywordsList == null)
                    keywordsList = hashSetOf()

                keywordsList.add(className)

                keywordIndexList[it] = keywordsList
            }
        }

        if (elements == null || elements.isEmpty())
            return false

        generateKtFile(keywordIndexList)

        return true
    }

    private fun generateKtFile(keywordIndexList: HashMap<String, HashSet<ClassName>?>) {

        val pkgName = keywordIndexList.values.first()!!.first().packageName
        val repoClass = ClassName(pkgName, INDEX_CLASS_NAME)

        val hashMapType = ClassName("kotlin.collections", "HashMap")
        val stringType = ClassName("kotlin", "String")
        val hashSetType = ClassName("kotlin.collections", "HashSet")
        val hashSetStringType = hashSetType.parameterizedBy(stringType)
        val repoClassIndexDBType = hashMapType.parameterizedBy(stringType, hashSetStringType)


        val typeSpec = TypeSpec.objectBuilder(repoClass)
            .addProperty(
                PropertySpec.builder(INDEX_CLASS_FIELD_NAME, repoClassIndexDBType)
                    .initializer("%L", "hashMapOf<String, HashSet<String>>()")
                    .build()
            ).addInitializerBlock(
                brewInitBlock(keywordIndexList)
            )
            .build()

        val ktFile = FileSpec.builder(pkgName, INDEX_CLASS_NAME)
            .addType(typeSpec)
            .build()

        ktFile.writeTo(mFiler!!)

    }

    private fun brewInitBlock(keywordIndexList: HashMap<String, HashSet<ClassName>?>): CodeBlock {
        val codeBuilder = CodeBlock.Builder()
        val formatStringBuilder = StringBuilder()
        var varargs: Array<String>
        keywordIndexList.keys.forEach {

            val size = if (keywordIndexList[it]!!.size > 0) keywordIndexList[it]!!.size else 0
            varargs = Array(1 + size) { "" }

            formatStringBuilder.append("$INDEX_CLASS_FIELD_NAME[\"%L\"] =\n hashSetOf(")

            varargs[0] = it

            messager?.printMessage(Diagnostic.Kind.NOTE, it)
            messager?.printMessage(Diagnostic.Kind.NOTE, "${keywordIndexList[it]?.toList()}")

            keywordIndexList[it]?.forEachIndexed { index, cls ->
                formatStringBuilder.append("\"%L\"")
                formatStringBuilder.append(",")
                formatStringBuilder.append("\n          ") // 10 spaces
                varargs[1 + index] = cls.canonicalName
            }
            if (size > 0) {
                formatStringBuilder.delete(
                    formatStringBuilder.lastIndex - 11,
                    formatStringBuilder.length
                )
            }
            formatStringBuilder.append(")\n\n")
            codeBuilder.add(formatStringBuilder.toString(), *varargs)

            formatStringBuilder.clear()

        }

        return codeBuilder.build()
    }

    private fun getPackageName(ele: Element): String? {
        if (ele is TypeElement) {
            val packageElement = elementUtils?.getPackageOf(ele)
            if (!packageElement?.isUnnamed!!) {
                return packageElement.qualifiedName.toString()
            }
        }
        return null
    }
}